package com.cn.wanxi.autoconfig.advice;

import com.cn.wanxi.autoconfig.annotation.PermissionCheck;
import com.cn.wanxi.autoconfig.annotation.RequestLog;
import com.cn.wanxi.autoconfig.exception.SelfException;
import com.cn.wanxi.autoconfig.vo.ResultVO;
import lombok.extern.log4j.Log4j2;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

/**
 * @ClassName PermissionAdvice
 * @Description 权限增强
 * @Author JiJiang
 * @Date 2023/7/5 22:17
 * @Version 1.0
 */
@Log4j2
@Aspect
@Component
public class PermissionAdvice {


    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

    private Date date = new Date();


    /**
     * 获取当前的时间
     *
     * @return
     */
    private String getCurrentTime() {
        date.setTime(System.currentTimeMillis());
        return dateFormat.format(date);

    }


    //region 前置


    //将带有 PermissionCheck 注解的类作为切点
    @Pointcut("@annotation(com.cn.wanxi.autoconfig.annotation.RequestLog)")
//    @Pointcut("@within(com.cn.wanxi.autoconfig.annotation.RequestLog)")
    public void pt_checkToken() {

    }

    @Before("pt_checkToken()")
    // 会报警告：argNames attribute isn't defined。
    // 解决方法：@Before(value = "pointcut(apiLog)", argNames = "joinPoint,apiLog")
    public void before(JoinPoint joinPoint) {


        String targetMethodName = joinPoint.getSignature().getName();

        String targetClzName = joinPoint.getSignature().getDeclaringTypeName();

//        System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
//        System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
//        System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
//        System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();


        log.info("【前置增强】当前的代理对象为：" + joinPoint.getThis() + " 被代理对象为：" + joinPoint.getTarget() + " 代理类的名称为：" + targetClzName + " 代理的方法为：" + targetMethodName);
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        RequestLog requestLog = method.getAnnotation(RequestLog.class);


        //如果不需要日志记录
        if (!requestLog.needLog()) {
            return;
        }

        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null) {
            ipAddress = request.getRemoteAddr();
        }


        String outInfo = "ip 为：\t" + ipAddress + "\t的客户端在\t" + getCurrentTime() + "\t时访问了类：\t" + targetClzName + "\t的接口：" + targetMethodName;

        log.info(outInfo);

    }
    //endregion

    //region 环绕通知

    //将controller包下面所有类的方法均定义为切点

    @Pointcut("execution(* com.cn.wanxi.autoconfig.controller.*.*(..))")
    public void pt_exception() {

    }

    @Around("pt_exception()")
    public Object throwSelfException(ProceedingJoinPoint pjp) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        Object result = null;
        Class<?> targetCls = null;
        MethodSignature ms = null;
        Method targetMethod = null;

        String targetClzName = pjp.getTarget().getClass().getName();

        String targetMethodName = pjp.getSignature().getName();

        log.info("【环绕增强】当前的代理对象为：" + pjp.getThis() + " 被代理对象为：" + pjp.getTarget() + " 代理类的名称为：" + targetClzName + " 代理的方法为：" + targetMethodName);

        try {
            //获取类的字节码对象，通过字节码对象获取方法信息
            targetCls = pjp.getTarget().getClass();

            //获取方法签名(通过此签名获取目标方法信息)
            ms = (MethodSignature) pjp.getSignature();

            //获取目标方法上的注解指定的操作名称
            targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());

            long startTime = System.currentTimeMillis();
            result = pjp.proceed();
            long endTime = System.currentTimeMillis();

            log.info("【控制层】类：{}执行方法：{}耗时时长为：{}ms", targetCls.getName(), targetMethod.getName(), (endTime - startTime));
            return result;
        } catch (Throwable throwable) {
            SelfException myException = new SelfException();

            //如果是从底层抛出的自定义异常
            if (throwable instanceof SelfException) {
                //强转
                myException = (SelfException) throwable;
            } else {
                myException.setCasePoint("\t控制层\t出现异常!!! \t类：" + targetCls.getName() + "执行\t方法:" + targetMethod.getName() + "\t时出现异常！！！");
                myException.setException(throwable);
            }

            throw myException;
        }

    }


    //endregion


    //region 返回前
    @Pointcut("execution(* com.cn.wanxi.autoconfig.controller.*.*(..))")
    public void pt_beforeReturn() {

    }

    @AfterReturning(value = "pt_beforeReturn()", returning = "result")
    public void beforeReturn(JoinPoint joinPoint, Object result) {


        //获取类的字节码对象，通过字节码对象获取方法信息
        Class targetCls = joinPoint.getTarget().getClass();
        String targetClzName = targetCls.getName();
        //获取方法签名(通过此签名获取目标方法信息)
        String targetMethodName = joinPoint.getSignature().getName();
        log.info("【返回增强】当前的代理对象为：" + joinPoint.getThis() + " 被代理对象为：" + joinPoint.getTarget() + " 代理类的名称为：" + targetClzName + " 代理的方法为：" + targetMethodName);

        if (result instanceof ResultVO) {

            ResultVO toRetData = (ResultVO) result;


            String msg = toRetData.getMsg();


            if (Strings.isEmpty(msg)) {
                msg = "被AfterReturning修改";
            }
            msg = Strings.isEmpty(msg) ? "被AfterReturning修改" : msg + "被AfterReturning修改";

            toRetData.setMsg(msg);
            result = toRetData;

        }


    }

    //endregion


    //region 后置
    @Pointcut("execution(* com.cn.wanxi.autoconfig.controller.*.*(..))")
    public void pt_after() {

    }

    @After(value = "pt_after()")
    public void after(JoinPoint joinPoint) {
        //获取类的字节码对象，通过字节码对象获取方法信息
        Class targetCls = joinPoint.getTarget().getClass();
        String targetClzName = targetCls.getName();
        //获取方法签名(通过此签名获取目标方法信息)
        String targetMethodName = joinPoint.getSignature().getName();
        log.info("【后置增强】当前的代理对象为：" + joinPoint.getThis() + " 被代理对象为：" + joinPoint.getTarget() + " 代理类的名称为：" + targetClzName + " 代理的方法为：" + targetMethodName);


    }


    //endregion


    //region 异常
    @Pointcut("execution(* com.cn.wanxi.autoconfig.controller.*.*(..))")
    public void pt_afterThrowing() {


    }

    @AfterThrowing(value = "pt_afterThrowing()", throwing = "exception")
    public void after(JoinPoint joinPoint, Exception exception) {

        //获取类的字节码对象，通过字节码对象获取方法信息
        Class targetCls = joinPoint.getTarget().getClass();
        String targetClzName = targetCls.getName();
        //获取方法签名(通过此签名获取目标方法信息)
        String targetMethodName = joinPoint.getSignature().getName();
        log.info("【异常增强】当前的代理对象为：" + joinPoint.getThis() + " 被代理对象为：" + joinPoint.getTarget() + " 代理类的名称为：" + targetClzName + " 代理的方法为：" + targetMethodName + "异常的信息如下：\n" + exception.getMessage());

    }

    //endregion


}
